[2025-08-11] CSRF&CORS Bypass

๐Ÿฆฅ ๋ณธ๋ฌธ

CSRF Token

๋™์ž‘ ํ๋ฆ„

  1. ๊ฐ™์€ ์˜ค๋ฆฌ์ง„์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ํŠน์ • Token์„ ์„ธ์…˜์— ์ €์žฅ
  2. ์„œ๋ฒ„๊ฐ€ HTML form ํƒœ๊ทธ์˜ hidden ์†์„ฑ์ด๋‚˜ ๋ฉ”ํƒ€ ํƒœ๊ทธ๋กœ ์ „๋‹ฌ
  3. ์š”์ฒญ ์‹œ ๋“ค์–ด์˜จ ํ† ํฐ๊ณผ ์„ธ์…˜์˜ ํ† ํฐ์„ ๋น„๊ต

์˜ˆ์‹œ

<?php
if (!isset($_SESSION["csrftoken"])) {
    $csrftoken = bin2hex(random_bytes(32));
    $_SESSION["csrftoken"] = $csrftoken;
} else {
    $csrftoken = $_SESSION["csrftoken"];
}
$method = $_SERVER["HTTP_METHOD"];
if ($method !== "GET" && $method !== "HEAD") {
    if (!isset($_POST["csrftoken"]) ||
        !hash_equals($csrftoken, $_POST["csrftoken"]) {
        header("HTTP/1.1 403 Forbidden");
        die("CSRF detected");
    }
    echo "Input value: ";
    echo htmlentities($_POST["query"], ENT_QUOTES|ENT_HTML5, 'utf-8');
}
?>
<form action="" method="POST">
    <input name="csrftoken" type="hidden" value="<?=htmlentities($csrftoken, ENT_QUOTES|ENT_HTML5, 'utf-8'); ?>">
    <input name="query" type="text" />
    <input type="submit" />
</form>

์žฅ์ 

  • ์‚ฌ์šฉ์ž์˜ ์ถ”๊ฐ€์ ์ธ ์ƒํ˜ธ์ž‘์šฉ ๋ถˆํ•„์š” : CAPTCHA, OTP, ์ด๋ฉ”์ผ ํ™•์ธ ๋“ฑ๊ณผ ๊ฐ™์€ ๋ณด์•ˆ ๋Œ€์ฑ… ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ์ถ”๊ฐ€ ์ธ์ฆ, ์ž…๋ ฅ, ํด๋ฆญ ๋“ฑ์ด ํ•„์š”ํ•˜์ง€๋งŒ CSRF Token์€ ์ž๋™์œผ๋กœ ํ† ํฐ ์ „์†ก
  • ์œ„์กฐ ์š”์ฒญ ์ฐจ๋‹จ : ๊ณต๊ฒฉ์ž๋Š” ํ† ํฐ๊ฐ’์„ ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์œ„์กฐ ๋ถˆ๊ฐ€
  • ๋ธŒ๋ผ์šฐ์ € ์˜์กด๋„ ๋‚ฎ์Œ : ํ† ํฐ ์ƒ์„ฑ ๋ฐ ๊ฒ€์ฆ ๋กœ์ง์„ ์„œ๋ฒ„๊ฐ€ ์ „๋‹ดํ•˜๋ฏ€๋กœ, ๋ธŒ๋ผ์šฐ์ €/ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ ๋ณ€ํ™”์™€ ์ƒ๊ด€์—†์ด ๋™์ž‘. SameSite ์ฟ ํ‚ค ์ •์ฑ… ๊ฐ™ ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋Šฅ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ž‘๋™

์ฃผ์˜ ์‚ฌํ•ญ

  • ์งง์€ CSRF Token : Brute-force attack์œผ๋กœ ํš๋“ํ•  ์ˆ˜ ์—†๋„๋ก ๊ธธ์–ด์•ผ ํ•จ
  • ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ CSRF Token : PRNG ๊ฐ™์€ ์˜์‚ฌ ๋‚œ์ˆ˜ ์ƒ์„ฑ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์˜ˆ์ธก ๊ฐ€๋Šฅ. CSPRNG ๊ฐ™์€ ๋‚œ์ˆ˜ ์ƒ์„ฑ๊ธฐ ์‚ฌ์šฉ
  • CSRF Token ์œ ์ถœ : CSRF Token์ด URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ง€๋Š” ๊ฒฝ์šฐ, ์ดํ›„ ๋‹ค๋ฅธ ๋งํฌ๋ฅผ ๋ฐฉ๋ฌธํ•˜์˜€์„ ๋•Œ Referer ํ—ค๋”์— Token ๋…ธ์ถœ
  • ๊ธด ์œ ํšจ์‹œ๊ฐ„์„ ๊ฐ€์ง„ CSRF Token : ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์•„์›ƒ ํ•˜๊ณ  ๋‚œ ๋’ค ์ƒˆ๋กœ์šด ์„ธ์…˜์ด ์ƒ์„ฑ๋˜๋„ ํ† ํฐ์ด ์œ ์ง€๋˜์–ด ๊ณต๊ฒฉ์ž๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

CORS Vulnerability

๊ต์ฐจ ์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ ๋ฅผ ์œ„ํ•ด postMessage, JSONP, CORS ์ •์ฑ… ๊ธฐ์ˆ  ๋“ฑ์ด ๋„์ž…

  • ์ทจ์•ฝ์ ์˜ ์ข…๋ฅ˜
    • ํ˜„์žฌ ์‚ฌ์ดํŠธ์—์„œ ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋กœ ์ •๋ณด ์œ ์ถœ (๊ธฐ๋ฐ€์„ฑ) : ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋กœ๋ถ€ํ„ฐ CORS ์š”์ฒญ์„ ๋ฐ›์„ ๋•Œ ๊ทธ Origin์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๊ฐ€ ์ง„ํ–‰๋˜์ง€ ์•Š๊ณ  ์‘๋‹ตํ•˜๊ฑฐ๋‚˜ Origin์— ์ œ์•ฝ์ด ์—†๋Š” ๊ฒฝ์šฐ
    • ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์—์„œ ํ˜„์žฌ ์‚ฌ์ดํŠธ ๋ณ€์กฐ (๋ฌด๊ฒฐ์„ฑ) : ์ถœ์ฒ˜์— ๋Œ€ํ•œ ์‹ ๋ขฐ์„ฑ์— ๋Œ€ํ•œ ์ œํ•œ๊ณผ XSS ํ•„ํ„ฐ ๋“ฑ ์‹ ๋ขฐํ•˜์ง€ ์•Š๋Š” ์ž…๋ ฅ์— ๋Œ€ํ•œ ๋ฐฉ์–ด๊ฐ€ ๋ณ‘ํ–‰

postMessage Vulnerability

  • ์ •์˜ : ์„œ๋กœ ๋‹ค๋ฅธ Origin ๊ฐ„์— ๋ฉ”์‹œ์ง€๋ฅผ ์ฃผ๊ณ  ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” API ๋ฐ ๋ฉ”์†Œ๋“œ.
    • ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•  ๋•Œ์—๋Š” ๋Œ€์ƒ ์œˆ๋„์šฐ์˜ postMessage๋ฅผ ํ˜ธ์ถœ. ์ˆ˜์‹ ํ•˜๋Š” ์œˆ๋„์šฐ๋Š” message ์ „์—ญ ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด ์ฒญ์ทจํ•˜์—ฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ
    • ๋ฌธ์ž์—ด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๊ฐ์ฒด๋ฅผ ์ฃผ๊ณ  ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ.
      • ํ•จ์ˆ˜, DOM ๋…ธ๋“œ ๊ฐ์ฒด, ํ”„๋กœํ† ํƒ€์ž… ๋ฐ get/set ์†์„ฑ ์ •๋ณด๋Š” ๋ณด๋‚ผ์ˆ˜ ์—†์Œ
      • ๊ฐ์ฒด๋Š” ๋ณต์‚ฌ ๋ฐฉ์‹์œผ๋กœ ์ „๋‹ฌํ•˜๋ฏ€๋กœ ์†ก์‹  ํ›„ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ๋ฐ˜์˜ X
  • targetWindow.postMessage(message, targetOrigin, [transfer])

    ๋ณ€์ˆ˜ ์„ค๋ช…
    targetWindow ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ๋Œ€์ƒ Window
    message ๋ฉ”์‹œ์ง€ ๊ฐ์ฒด (ํ•จ์ˆ˜, DOM ๊ฐ์ฒด ๋“ฑ์€ ๋ณด๋‚ผ ์ˆ˜ ์—†์Œ)
    targetOrigin ๋ฉ”์‹œ์ง€ ์†ก์‹  ์‹œ์ ์— targetWindow์˜ Origin์ด targetOrigin๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•จ. targetOrigin์— "*"๋ฅผ ์ง€์ •ํ•˜๋ฉด Origin ๊ฒ€์‚ฌ๊ฐ€ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์Œ
    transfer (์„ ํƒ์‚ฌํ•ญ) ArrayBuffer๋‚˜ canvas context ๋“ฑ ์†Œ์œ ๊ถŒ์„ ์ „์ดํ•  ๊ฐ์ฒด์˜ ๋ฐฐ์—ด์„ ์ง€์ •
  • MessageEvent

    ๊ณ ์œ  ์†์„ฑ ์„ค๋ช…
    origin ๋ฉ”์‹œ์ง€๋ฅผ ์†ก์‹ ํ•œ Origin ๋ฐ˜ํ™˜
    source ๋ฉ”์‹œ์ง€๋ฅผ ์†ก์‹ ํ•œ Window ๊ฐ์ฒด ๋ฐ˜ํ™˜
    data ๋ณต์‚ฌ๋œ ๋ฉ”์‹œ์ง€ ๊ฐ์ฒด ๋˜๋Š” ๊ฐ’ ๋ฐ˜ํ™˜
      // ๋ฉ”์‹œ์ง€ ์†ก์‹ 
      targetWindow.postMessage(message, targetOrigin);
      // ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ 
      window.onmessage = function (e) {
          if (e.origin === 'https://dreamhack.io') {
              console.log(e.data);
              e.source.postMessage('Hello, world!', e.origin);
          }
      }
    
  • Origin ์ „ํ™˜ ๊ฒฝํ•ฉ ์กฐ๊ฑด : ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋Š” ๋Œ€์ƒ์€ ์ถœ์ฒ˜๊ฐ€ ๊ณ ์ •๋œ ์›น ๋ฌธ์„œ๊ฐ€ ์•„๋‹Œ ์ฐฝ(์œˆ๋„์šฐ) ์ฐฝ์˜ ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํ•˜์ดํผ๋งํฌ๋ฅผ ๋ฐฉ๋ฌธํ•˜๊ฑฐ๋‚˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‹œ Origin์ด ๋ณ€๊ฒฝ โ†’ ์˜๋„ํ•˜์ง€ ์•Š์€ Origin์œผ๋กœ ๋ฉ”์‹œ์ง€ ์ „์†ก
    • ๊ณต๊ฒฉ ์‹œ๋‚˜๋ฆฌ์˜ค
      1. ๋ถ€๋ชจ ์œˆ๋„์šฐ์—์„œ ์ž์‹ ์œˆ๋„์šฐ ์ƒ์„ฑ
      2. ์ž์‹ ์œˆ๋„์šฐ๊ฐ€ ๋ถ€๋ชจ ์œˆ๋„์šฐ๋กœ postMessage๋กœ ๋ฉ”์‹œ์ง€ ๋ฐ ๋น„๋ฐ€๊ฐ’ ์ „์†ก
      3. ๋ถ€๋ชจ ์œˆ๋„์šฐ๊ฐ€ ๊ณต๊ฒฉ์ž์˜ ๋‹ค๋ฅธ ์›น ์‚ฌ์ดํŠธ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
      4. ์ž์‹ ์œˆ๋„์šฐ๊ฐ€ ๊ณ„์† ๋ณด๋‚ด๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๊ณต๊ฒฉ์ž์˜ ์‚ฌ์ดํŠธ๋กœ ์ˆ˜์‹ 

JSONP Vulnerability

  • Origin ๊ฒ€์‚ฌ ๋ถ€์žฌ : JSONP์— ํ•œ์ •๋œ ์ทจ์•ฝ์ ์€ ์•„๋‹˜. ํ•˜์ง€๋งŒ JSONP ํŠน์„ฑ์ƒ CSRF ๊ณต๊ฒฉ์— ๋” ์ทจ์•ฝํ•จ
    • CSRF Token ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฉ์–ด
  • ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ช… ๊ฒ€์ฆ ๋ถ€์žฌ๋กœ ์ธํ•œ ์ œ๊ณต์ž XSS : ์ฝœ๋ฐฑ๋ช…์— HTML ์ฝ”๋“œ ๋“ฑ์„ ์‚ฝ์ž…ํ•˜์—ฌ HTML๋กœ ์ธ์‹  โ†’ XSS ์ทจ์•ฝ์  ๋ฐœ์ƒ
    • ํ•„ํ„ฐ ์ ์šฉ
      • HTTP Accept ํ—ค๋”์— text/javascript MIME ํƒ€์ž…์ด ํฌํ•จ๋˜์–ด ์žˆ๋Š” ์ง€ ๊ฒ€์‚ฌ
      • Content-Type: text/javascript ์„ค์ • ๋ฐ X-Content-Type-Options: nosniff ํ—ค๋”๋กœ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์ฝ˜ํ…์ธ ๋กœ ์ธ์‹๋˜๋Š” ๊ฒฝ์šฐ ๋ฐฉ์ง€
        • MIME Type sniffing : ์„œ๋ฒ„์—์„œ ๋ฐ›์€ Content-Type์„ ์‹ ๋ขฐํ•˜์ง€ ์•Š๊ณ  ๋ฆฌ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์ ์ ˆํ•œ ํ˜•์‹์œผ๋กœ ํ•ด์„ํ•˜๋Š” ๊ฒƒ. nosniff ๋กœ ์„ค์ •ํ•˜์—ฌ ์„œ๋ฒ„์—์„œ ์ „๋‹ฌ ๋ฐ›์€ Content-Type ๋งŒ์„ ๋ฏฟ์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•จ
  • JSONP API ์นจํ•ด ์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ ์ด์šฉ์ž XSS : JSONP API๊ฐ€ ์นจํ•ด ์‚ฌ๊ณ ๋ฅผ ๋‹นํ•ด ์•…์˜์ ์ธ ์‘๋‹ต์ด ์˜ค๋Š” ๊ณต๊ฒฉ โ†’ JSONP๊ฐ€ ์•„๋‹Œ CORS ์ •์ฑ… ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

Categories:

Updated:

Leave a comment